--[[
机器人下注操作ai


EV值	翻牌	转牌	翻牌前
>200	20	20	20
>150	15	15	15
>120	10	10	10
>100	5	5	5
>80	0	0	0
>50	-5	-5	-5
<=50	-15	-15	-15


需要跟注筹码=0时，不用计算胜率，权重直接为0
EV值=胜率（0-1之间）*池子总筹码/需要跟注筹码
翻牌前EV计算=0.33*池子总筹码/需要跟注筹码

名次	权重加成（每一轮次）
1	2
<=floor(人数*30%)	1
<=floor(人数*60%)	-1
其它	-2

最终名次	权重加成（每一轮次）
1	1
<=floor(人数*30%)	0.5
<=floor(人数*60%)	-0.5
其它	-1

权重=名次权重*场次比例
弃牌概率=初始权重-权重加成*1.5-赔率值权重
跟注概率=初始权重+权重加成*0.5+赔率值权重*0.5
加注概率=初始权重+权重加成*1+赔率值权重*0.5
起手牌举例计算（当前轮次1，最终名次1，赔率值=110）：
用户当前弃牌概率=50-20*1.5-10*1.5-5=0
用户当前跟注或看牌概率=30+20*0.5+10*0.5+2.5=47.5%
用户当前加注概率=20+20*1+10*1+2.5=52.5%

--]]


local cjson = require("cjson")
local math_random = math.random
local math_ceil = math.ceil
local string_format = string.format
local table_insert = table.insert

local M = class("Weight_Oper")

--操作概率
M.s_stage_rates = {}
M.s_stage_rates["preflop"] = {fold = 50, check = 30, raise = 20}
M.s_stage_rates["flop"]     = {fold = 30, check = 40, raise = 30}
M.s_stage_rates["turn"]     = {fold = 30, check = 40, raise = 30}
M.s_stage_rates["river"]    = {fold = 30, check = 40, raise = 30}

M.s_curr_weight = {}
M.s_curr_weight[1] = {rank = 1, val = 2}
M.s_curr_weight[2] = {area = 0.3, val = 1}
M.s_curr_weight[3] = {area = 0.6, val = -1}
M.s_curr_weight[4] = {area = 10000, val = -2}

M.s_final_weight = {}
M.s_final_weight[1] = {rank = 1, val = 1}
M.s_final_weight[2] = {area = 0.3, val = 0.5}
M.s_final_weight[3] = {area = 0.6, val = -0.5}
M.s_final_weight[4] = {area = 10000, val = -1}

M.s_ev_weight = {}
M.s_ev_weight[1] = {min = -1000000000,  max = 50,         preflop = -15,   flop = -15,   turn = -15}
M.s_ev_weight[2] = {min = 51,           max = 80,         preflop = -5,    flop = -5,    turn = -5}
M.s_ev_weight[3] = {min = 81,           max = 100,        preflop = 0,     flop = 0,     turn = 0}
M.s_ev_weight[4] = {min = 101,          max = 120,        preflop = 5,     flop = 5,     turn = 5}
M.s_ev_weight[5] = {min = 121,          max = 150,        preflop = 10,    flop = 10,    turn = 10}
M.s_ev_weight[6] = {min = 151,          max = 200,        preflop = 15,    flop = 15,    turn = 15}
M.s_ev_weight[7] = {min = 201,          max = 1000000000, preflop = 20,    flop = 20,    turn = 20}

M.s_raise_bb_list = {3, 5, 10, 15}
M.s_raise_pot_list = {0.3333, 0.5, 0.6666, 1}       -- 1/3  1/2  2/3  1

M.s_card_points = {}
M.s_card_points[2] = "2"
M.s_card_points[3] = "3"
M.s_card_points[4] = "4"
M.s_card_points[5] = "5"
M.s_card_points[6] = "6"
M.s_card_points[7] = "7"
M.s_card_points[8] = "8"
M.s_card_points[9] = "9"
M.s_card_points[10] = "T"
M.s_card_points[11] = "J"
M.s_card_points[12] = "Q"
M.s_card_points[13] = "K"
M.s_card_points[14] = "A"

M.s_card_colors = {}
M.s_card_colors[1] = "s"
M.s_card_colors[2] = "h"
M.s_card_colors[3] = "c"
M.s_card_colors[4] = "d"

-- 按 buyin 来进行分类
M.s_weight_ratios = {}        --  权重系数 用于调节难度 1 ... 10
M.s_weight_ratios[1000000] = 2
M.s_weight_ratios[2000000] = 3
M.s_weight_ratios[5000000] = 4
M.s_weight_ratios[10000000] = 5
M.s_weight_ratios[20000000] = 6

function M:ctor()
    self.fpath = g.getenv("base_root") --框架所在目录
    self.py = self.fpath .. "depends/pylib/holdem/holdem_calc.pyc"
    self.final_rank = 999 --最终于排名


end

function M:getWeightRatio(personality)
    --return self.s_weight_ratios[params["buyin"]] or 10
    --local level = g.room_cfg.level or math_random(1, 4)
    local level = personality
    local weights = {
        1, 2, 3,4,5,6,7,8,9,10,11,
    }
    local weight = weights[level] or 10
    return weight
end

--知道自己要下的额度( 存在计算时间过久的可能
function M:getBetChip(user)
    --local star_time = g.time()
    local mid = user:getUid()
    local personality = user:getPersonality() --性格
    if not g.desk:hasPlayer() then
        --无真人时，1v1的时候
        personality = 1
    end
    local chips = 0
    local reqData = self:_weight_getTablePaisInfo()
    if not reqData or not reqData["logid"] then return chips end
    local weight_ratio = self:getWeightRatio(personality)
    local action = self:_weight_calcAction(mid, reqData, weight_ratio)
    local user = g.user_mgr:getUser(mid)
    if action == "fold" then
        chips = -1
        local user_chip = user:getChip()
        local user_stage_bet = user:getStageBet()
        if g.desk.follow_bet == 0 then chips = 0 end
        --如果已下注了很多剩下一点点就不要弃牌了
        if user:getSeatBet() > user_chip*5 then
            chips = user:getChip()
        end
        if g.desk.stage_bet - user_stage_bet <= user_stage_bet*0.2 then
            --差一点就可以跟注
            chips = g.desk.stage_bet - user_stage_bet
        end
        if g.desk:getStage() == "preflop" then
            --筹码比较多的情况积极入局
            --if g.desk.follow_bet < 1000 and user_chip > g.desk.follow_bet * 10  then
            --    if math_random(1, 3) == 3 then
            --        chips = g.desk.follow_bet
            --    end
            --end
        end
    elseif action == "check" then
        chips = g.desk.follow_bet

    elseif action == "raise" then
        local rand = math_random(1, #self.s_raise_bb_list)
        local raiseCount = reqData["stage_raise_count"]
        if reqData["stage"] == "preflop" and raiseCount == 0 then
            chips = math_ceil(self.s_raise_bb_list[rand] * reqData["bb"])
        else
            chips = math_ceil(self.s_raise_pot_list[rand] * reqData["pot"])
        end
        if chips < g.desk.min_raise_bet then
            --最小加注额度
            chips = g.desk.min_raise_bet
        end
    else
        --无操作
        chips = -1
        if g.desk.follow_bet == 0 then chips = 0 end
    end
    if chips > user:getChip() then
        chips = user:getChip()
    end

    --g.log:debug("5robot getBetChip:", mid, weight_ratio, action, chips, g.time()-star_time)
    return chips
end

--桌子的信息
function M:_weight_getTablePaisInfo()
    local data = {}
    data["logid"] = g.desk:getRoundId()
    data["stage"] = g.desk:getStage()
    data["bb"] = g.desk.bb
    data["common_cards"] = g.desk.common_cards
    data["stage_raise_count"] = g.desk.stage_bet_count
    data["pot"] = g.desk.total_bet
    data["pais"] = {}
    local users = g.user_mgr:getPlayingUser()
    for _, v in pairs(users) do
        local paisData = {
            mid = v:getUid(),
            curr_rank = v:getHandRank(), --9999,
            --final_rank = v:getRank(),
            final_rank = v:getFinalRank(),
            hand_cards = v:getCards(),
            seat_chips = v:getChip(),
        }
        table_insert(data["pais"], paisData)
    end
    data["buyin"] = g.room_cfg.min_buy
    data.follow = g.desk.follow_bet

    return data
end

--根据权重计算操作
function M:_weight_calcAction(mid, params, weight_ratio)
    local stageData = self.s_stage_rates[params["stage"]]
    if not stageData then return end
    local seatCount = #params["pais"]
    local selfData = {}
    local curr_ranks, final_ranks, other_cards_list = {}, {}, {}
    for _, v in pairs(params["pais"]) do
        if mid == v.mid then
            selfData = v
        else
            table.extend(other_cards_list, v["hand_cards"])
        end
        table_insert(curr_ranks, v["curr_rank"])
        table_insert(final_ranks, v["final_rank"])
    end
    local curr_weight, curr_rank = self:_weight_getWeight(selfData["curr_rank"], curr_ranks, seatCount, self.s_curr_weight)
    local final_weight, final_rank = self:_weight_getWeight(selfData["final_rank"], final_ranks, seatCount, self.s_final_weight)
    local total_weight = curr_weight + final_weight
    local ev_weight = self:_weight_getEvWeight(params, selfData, other_cards_list)

    self.final_rank = final_rank

    --local str = string_format("_weight_calcAction weight rid=%d mid=%d logid=%d stage=%s " ..
    --        "total_weight=%s seat_count=%d curr_rank=%d final_rank=%d ev_weight=%d weight_ratio=%d",
    --        g.rid, mid, params["logid"], params["stage"], total_weight, seatCount, curr_rank, final_rank, ev_weight, weight_ratio)
    --g.log:warn("1weight_bot_" .. g.rid, str)

    total_weight = total_weight * weight_ratio
    local fold = stageData["fold"] - total_weight * 1.5 - ev_weight
    local check = stageData["check"] + total_weight * 0.5 + ev_weight * 0.5
    local raise = stageData["raise"] + total_weight * 1 + ev_weight * 0.5
    local rand = math_random(1, 100)
    --local str = string_format("2weight_bot_" .. g.tid.. "_weight_calcAction rate rid=%d mid=%d fold=%s check=%s raise=%s rand=%d",
    --        g.rid, mid, fold, (fold + check), (fold + check + raise), rand)
    --g.log:warn(str)

    if rand <= fold then
        return "fold"
    elseif rand <= (fold + check) then
        if final_rank ~= 1 and g.desk:hasPlayer() then
            --如果在river圏没有中牌就不要call大额度注了
            local stage = g.desk:getStage()
            if stage == g.desk.s_stage_turn or stage == g.desk.s_stage_river then
                --g.log:deskWarn("111111111111111final_rank", final_rank)
                return "fold"
            end
        end

        return "check"
    end

    if final_rank ~= 1 and g.desk:hasPlayer() then
        --如果在river圏没有中牌就不要call大额度注了
        local stage = g.desk:getStage()
        local rand_num = math.random(1, 100)
        if stage == g.desk.s_stage_turn or stage == g.desk.s_stage_river and rand_num < 70 then
            --g.log:deskWarn("2222222222222222222222222final_rank", final_rank)
            return "check"
        end
        --前圏不要太冲动的加注
        if stage == g.desk.s_stage_preflop and rand_num < 50 then
            return "check"
        end
        if stage == g.desk.s_stage_flop and rand_num < 20 then
            return "check"
        end
    end


    return "raise"
end

function M:_weight_getWeight(val, lists, seatCount, conf)
    local weight_val = 0
    local rank = self:_weight_getRank(val, lists)
    local weight_conf = {}
    for k, v in ipairs(conf) do
        if v["rank"] then
            weight_conf[k] = v
        elseif v["area"] then
            weight_conf[k] = {
                rank = math_ceil(seatCount * v["area"]),
                val = v["val"]
            }
        end
    end
    for _, v in ipairs(weight_conf) do
        if rank <= v["rank"] then
            weight_val = v["val"]
            break
        end
    end
    return weight_val, rank
end

--排名权重
function M:_weight_getRank(val, lists)
    local rank = 999
    --小到大
    table.sort(lists, function(a, b)
        return a < b
    end)
    for k, v in ipairs(lists) do
        if v == val then
            rank = k
            break
        end
    end
    return rank
end

--ev值权重
function M:_weight_getEvWeight(params, selfData, otherCardsList)
    local stage = params["stage"]
    local pots = params["pot"]
    local commonCards = params["common_cards"]
    local followChips = params.follow --跟注额
    local weight = 0
    local selfHandCards = selfData.hand_cards
    local seat_chips = selfData.seat_chips
    --print("1111111111",stage, followChips)
    if stage == "river" then return weight end
    if not followChips or not seat_chips then
        g.log:err("_weight_getEvWeight: ", followChips, seat_chips, selfData)
        return weight
    end
    if followChips == 0 then return weight end
    if followChips > seat_chips then
        followChips = seat_chips
    end
    local odds = pots / followChips
    local winRate = 0.33
    if stage ~= "preflop" then
        --print("1111111111222222",stage)
        winRate = self:_weight_cmdShell(selfHandCards, otherCardsList, commonCards)
    end
    local ev = odds * winRate * 100
    --[[
    logger.warn("weight_bot_" .. self.info.seatData.rid, 
                    "_weight_getEvWeight weight rid=%d mid=%d pots=%s follow=%s odds=%.4f win_rate=%.4f ev=%.4f", 
                                        self.info.seatData.rid, self.info.mid, pots, followChips, odds, winRate, ev)
    ]]
    local conf = nil
    for _, v in pairs(self.s_ev_weight) do
        if ev >= v["min"] and ev <= v["max"] then
            conf = v
            break
        end
    end
    if not conf then return weight end
    if not conf[stage] then return weight end
    return conf[stage]

end

--胜率计算
function M:_weight_cmdShell(selfHandCards, otherCardsList, commonCards)
    --local start_time = g.time()
    --g.log:debug("_weight_cmdShell:", selfHandCards, otherCardsList, commonCards)
    local function getVal(a)
        return a & 0x0F
    end
    local function getColor(a)
        return (a & 0xF0)/16 + 1
    end
    local cardsList = {}
    local commonList = {}
    local c
    for _, v in pairs(selfHandCards) do
        c = string_format("%s%s", self.s_card_points[getVal(v)], self.s_card_colors[getColor(v)])
        table_insert(cardsList, c)
    end
    for _, v in pairs(otherCardsList) do
        c = string_format("%s%s", self.s_card_points[getVal(v)], self.s_card_colors[getColor(v)])
        table_insert(cardsList, c)
    end
    for _, v in pairs(commonCards) do
        c = string_format("%s%s", self.s_card_points[getVal(v)], self.s_card_colors[getColor(v)])
        table_insert(commonList, c)
    end

    local sh = string_format("python %s %s -b %s", self.py, table.concat(cardsList, " "), table.concat(commonList, " "))
    local fd = io.popen(sh, "r")
    local output = fd:read("*a")
    fd:close()
    --print("1111111111111111111122222233", output)
    local isJson, detail = pcall(cjson.decode, output)
    if not isJson or not detail then
        g.log:err("weight_json_error", "_weight_cmdShell sh = %s", sh)
        return 0
    end
    --local end_time = g.time()
    --g.log:debug("_weight_cmdShell:", sh, end_time - start_time)
    return detail[1] or 0
end

return M